#!/bin/bash
#
#  Copyright (c) 2020, The OpenThread Authors.
#  All rights reserved.
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are met:
#  1. Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#  2. Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#  3. Neither the name of the copyright holder nor the
#     names of its contributors may be used to endorse or promote products
#     derived from this software without specific prior written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
#  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
#  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
#  POSSIBILITY OF SUCH DAMAGE.
#

# ==============================================================================
# Bash definitions
# ==============================================================================
if [[ -n ${BASH_SOURCE[0]} ]]; then
    script_path="${BASH_SOURCE[0]}"
else
    script_path="$0"
fi
script_dir="$(realpath "$(dirname "${script_path}")")"
repo_dir="$(dirname "${script_dir}")"

# CMake Build options
OT_OPTIONS=(
    "-DCMAKE_BUILD_TYPE=Release"
    "-DOT_DIAGNOSTIC=ON"
    "-DOT_EXTERNAL_HEAP=ON"
    "-DOT_SLAAC=ON"
)

# Check if ccache is installed and use it if available
if command -v ccache >/dev/null; then
    OT_OPTIONS+=("-DCMAKE_C_COMPILER_LAUNCHER=ccache")
    OT_OPTIONS+=("-DCMAKE_CXX_COMPILER_LAUNCHER=ccache")
fi
readonly OT_OPTIONS

# ==============================================================================
# shellcheck source=script/efr32-definitions
source "${repo_dir}/script/efr32-definitions"

# shellcheck source=script/slc_cli
source "${repo_dir}/script/slc_cli"

# shellcheck source=script/util
source "${repo_dir}/script/util"

set -euxo pipefail

# ==============================================================================
# Pre-build checks
# ==============================================================================
# Initialize the Silicon Labs SDK
# This function initializes the Silicon Labs SDK submodule and checks if the platform is supported
# Arguments:
#   $1: The board name
# Returns:
#   None
init_silabs_sdk()
{
    set +x
    echo "========================================================================================================="
    echo "Ensure SDK submodule has been initialized"
    echo "========================================================================================================="
    set -x
    # Skip LFS files for now. They will be pulled in init_silabs_sdk_lfs()
    GIT_LFS_SKIP_SMUDGE=true git submodule update --init "${sdk_dir}"

    set +x
    echo "========================================================================================================="
    echo "Check if the Git LFS package is installed"
    echo "========================================================================================================="
    set -x
    if ! git lfs >/dev/null; then
        set +x
        echo "ERROR: Git LFS is not installed"
        echo
        echo "Please run './script/bootstrap packages'" to install it
        exit 3
    fi

    set +x
    echo "========================================================================================================="
    echo "Ensure Git LFS has been initialized for the SDK"
    echo "========================================================================================================="
    set -x
    if [ ! -f "$(git -C "${sdk_dir}" rev-parse --git-dir)/hooks/pre-push" ]; then
        git -C "${sdk_dir}" lfs install
    fi

    set +x
    echo "========================================================================================================="
    echo "Ensure LFS objects have been pulled"
    echo "========================================================================================================="
    set -x

    # TODO: Add .lfsconfig lines to only fetch the necessary files
    git -C "${sdk_dir}" lfs pull
}

# ==============================================================================
# Build functions
# ==============================================================================
generate()
{
    if [ "${skip_generation}" = true ]; then
        return
    fi

    if contains ".*-(ftd|mtd)" "${target_apps[@]}"; then
        set +x
        echo "========================================================================================================="
        echo "Generate openthread-efr32-soc and openthread-efr32-soc-mbedtls libs"
        echo "========================================================================================================="
        set -x
        "${repo_dir}/script/generate" \
            "${repo_dir}/third_party/silabs/slc/platform_projects/openthread-efr32-soc.slcp" \
            "${slc_generated_projects_dir}/soc" \
            "${board}"
    fi
    if contains "ot-rcp-uart" "${target_apps[@]}"; then
        set +x
        echo "========================================================================================================="
        echo "Generate openthread-efr32-rcp-uart and openthread-efr32-rcp-uart-mbedtls libs"
        echo "========================================================================================================="
        set -x
        "${repo_dir}/script/generate" \
            "${repo_dir}/third_party/silabs/slc/platform_projects/openthread-efr32-rcp-uart.slcp" \
            "${slc_generated_projects_dir}/rcp_uart" \
            "${board}"
    fi
    if contains "ot-rcp-spi" "${target_apps[@]}"; then
        set +x
        echo "========================================================================================================="
        echo "Generate openthread-efr32-rcp-spi and openthread-efr32-rcp-spi-mbedtls libs"
        echo "========================================================================================================="
        set -x
        "${repo_dir}/script/generate" \
            "${repo_dir}/third_party/silabs/slc/platform_projects/openthread-efr32-rcp-spi.slcp" \
            "${slc_generated_projects_dir}/rcp_spi" \
            "${board}"
    fi
}

build_rcp_uart()
{
    set +x
    echo "======================================================================"
    echo "Building ot-rcp (UART):"
    echo "======================================================================"
    set -x
    builddir="${OT_CMAKE_BUILD_DIR:-$repo_dir/build/${board}}/openthread/rcp_uart"

    mkdir -p "${builddir}"
    cd "${builddir}"

    cmake \
        -GNinja \
        -DOT_PLATFORM_LIB_DIR="${slc_generated_projects_dir}"/rcp_uart \
        -DOT_FTD=OFF \
        -DOT_MTD=OFF \
        -DOT_RCP=ON \
        -DOT_APP_CLI=OFF \
        -DOT_APP_NCP=OFF \
        -DOT_APP_RCP=ON \
        -DOT_COMPILE_WARNING_AS_ERROR=ON \
        "$@" "${repo_dir}" \
        --graphviz=graph.dot

    ninja "ot-rcp"
    create_srec "${builddir}"
    cd "${repo_dir}"
}

build_rcp_spi()
{
    set +x
    echo "======================================================================"
    echo "Building ot-rcp (SPI)"
    echo "======================================================================"
    set -x
    builddir="${OT_CMAKE_BUILD_DIR:-$repo_dir/build/${board}}/openthread/rcp_spi"

    mkdir -p "${builddir}"
    cd "${builddir}"

    cmake \
        -GNinja \
        -DOT_PLATFORM_LIB_DIR="${slc_generated_projects_dir}"/rcp_spi \
        -DOT_FTD=OFF \
        -DOT_MTD=OFF \
        -DOT_RCP=ON \
        -DOT_APP_CLI=OFF \
        -DOT_APP_NCP=OFF \
        -DOT_APP_RCP=ON \
        -DOT_COMPILE_WARNING_AS_ERROR=ON \
        -DOT_NCP_SPI=ON \
        "$@" "${repo_dir}" \
        --graphviz=graph.dot

    ninja "ot-rcp"
    create_srec "${builddir}"
    cd "${repo_dir}"
}

build_soc()
{
    set +x
    echo "======================================================================"
    echo "Building SoC apps:"
    for t in "${soc_targets[@]}"; do
        echo " - ${t}"
    done
    echo "======================================================================"
    set -x

    builddir="${OT_CMAKE_BUILD_DIR:-$repo_dir/build/${board}}/openthread/soc"

    mkdir -p "${builddir}"
    cd "${builddir}"

    cmake \
        -GNinja \
        -DOT_PLATFORM_LIB_DIR="${slc_generated_projects_dir}"/soc \
        -DOT_FTD=ON \
        -DOT_MTD=ON \
        -DOT_RCP=OFF \
        -DOT_APP_CLI=ON \
        -DOT_APP_NCP=ON \
        -DOT_APP_RCP=OFF \
        -DOT_COMPILE_WARNING_AS_ERROR=ON \
        "$@" "${repo_dir}" \
        --graphviz=graph.dot

    ninja "${soc_targets[@]}"

    create_srec "${builddir}"
    cd "${repo_dir}"
}

# ==============================================================================
main()
{
    local usage="usage: $0 [-h] [--skip-silabs-apps] <brdXXXXy> [-D<OT_XXXX=ON> -D<OT_YYYY=OFF>]"

    local skip_silabs_apps=false
    skip_generation=false
    # Parse flags
    optspec=":h-:"
    while getopts "$optspec" optchar; do
        case "${optchar}" in
            -)
                case "${OPTARG}" in
                    skip-generation)
                        printf '\n\nSkipping SLC generation...\n\n' >&2
                        skip_generation=true
                        shift 1
                        ;;
                    skip-silabs-apps)
                        printf '\n\nSkipping silabs example apps...\n\n' >&2
                        skip_silabs_apps=true
                        shift 1
                        ;;
                    *)
                        echo "Unknown option --${OPTARG}" >&2
                        exit 2
                        ;;
                esac
                ;;
            h)
                echo "${usage}" >&2
                exit 2
                ;;
        esac
    done

    # Add a vendor slc extension if the path to one is defined
    if [ -n "${VENDOR_EXTENSION-}" ]; then
        echo "Vendor SLC extension found: ${VENDOR_EXTENSION}"
        parse_configuration "${VENDOR_EXTENSION}"
        echo " - Board: ${board}"
    else
        # Parse board
        lowercase_args=$(echo "$@" | tr '[:upper:]' '[:lower:]')
        board=$([[ ${lowercase_args} =~ (brd[0-9]{4}[a-z]{1}) ]] && echo "${BASH_REMATCH[1]}")
        shift
    fi

    # Check if a platform can be found for a given board in Simplicity SDK
    platform=$(efr32_get_platform "${board}" "${sisdk_dir}")

    # Check if the platform is supported
    efr32_check_platform "${platform}" || die "Unsupported platform ${platform}"

    set +x
    echo "========================================================================================================="
    echo "Using Simplicity SDK for board ${board} (${platform})"
    echo "========================================================================================================="
    set -x
    # Initialize the SDK
    init_silabs_sdk "${board}"

    local options=("${OT_OPTIONS[@]}")

    options+=("-DCMAKE_TOOLCHAIN_FILE=src/${platform}/arm-none-eabi.cmake")

    # Set the target apps
    if [ -z ${OT_CMAKE_NINJA_TARGET:+x} ]; then
        # Default target apps
        case "${platform}" in
            efr32mg2*)
                target_apps=("ot-rcp-uart" "ot-rcp-spi" "ot-cli-ftd" "ot-cli-mtd" "ot-ncp-ftd" "ot-ncp-mtd")
                ;;
        esac
    else
        # Use the specified target apps
        target_apps=("${OT_CMAKE_NINJA_TARGET[@]}")
    fi

    options+=("$@")
    export OT_CMAKE_BUILD_DIR="$repo_dir/build/${board}"
    slc_generated_projects_dir="${OT_CMAKE_BUILD_DIR}"/slc

    # Generate the platform libs and related libs
    if [ -n "${VENDOR_EXTENSION-}" ]; then
        "${VENDOR_EXTENSION}"/script/generate
        options+=("-DVENDOR_EXTENSION=${VENDOR_EXTENSION}")

        # TODO: Fix 'script/build_example_apps' so that it works with vendor platform libs
        skip_silabs_apps=true
    else
        generate
    fi

    # Separate target_apps into two lists
    rcp_targets=()
    soc_targets=()
    for t in "${target_apps[@]}"; do [[ $t =~ .*rcp.* ]] && rcp_targets+=("$t") || soc_targets+=("$t"); done

    # Build ot-rcp targets
    if contains "ot-rcp-uart" "${rcp_targets[@]-}"; then
        build_rcp_uart -DEFR32_PLATFORM="${platform}" -DBOARD="${board}" "${options[@]}"
    fi
    if contains "ot-rcp-spi" "${rcp_targets[@]-}"; then
        build_rcp_spi -DEFR32_PLATFORM="${platform}" -DBOARD="${board}" "${options[@]}"
    fi
    # Build soc targets
    if [ ! ${#soc_targets[@]} -eq 0 ]; then
        build_soc -DEFR32_PLATFORM="${platform}" -DBOARD="${board}" "${options[@]}"
    fi

    # Build silabs apps if no target apps are specified and the flag is not set
    if [ -z ${OT_CMAKE_NINJA_TARGET:+x} ] && [ "${skip_silabs_apps}" = false ]; then
        local before_flags=()
        local after_flags=()

        if [ "${skip_generation}" = true ]; then
            before_flags+=("--skip-generation")
        fi

        "${repo_dir}"/script/build_example_apps "${before_flags[@]-}" "${board}" "${after_flags[@]-}" "$@"
    fi

    ls -alh "${OT_CMAKE_BUILD_DIR}"/openthread/*/bin/*

}

cleanup()
{
    # Placeholder for any cleanup tasks
    :
}

trap cleanup EXIT

main "$@"
